"use strict";
/**
 * @ Author: Yoann Meclot. MSay2
 * @ Created on: 2020-06-04 05:54:17
 * @ Modified on: 2021-06-14 17:57:34
 */
/**
 * Copyright (c) 2020-2021 MSay2 (Yoann Meclot) - @msay2/tspoet
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to set private field on non-instance");
    }
    privateMap.set(receiver, value);
    return value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return privateMap.get(receiver);
};
var _mValues, _mIndex, _mSize;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinkedMap = exports.Map = exports.MapType = void 0;
const Argument_js_1 = require("../Argument.js");
const Exceptions_js_1 = require("../Exceptions.js");
const StringBuilder_js_1 = require("../StringBuilder.js");
const Utils_js_1 = require("../Utils.js");
/**
 * The EntryIterable class is a mean to reiterate the entries to the map.
 *
 * It allows to you obtain eachs values and eachs keys of eachs entries of the map
 * with its respectives methods (#method Entry.getKey) and (#method Entry.getValue).
 *
 * @class EntryIterable
 * @implements {Iterable}
 * @version 1.1
 * @since 1.5
 */
class EntryIterable {
    constructor(keys, values, size) {
        this.keys = [];
        this.values = [];
        this.index = -1;
        this.size = 0;
        this.size = size;
        this.keys = keys;
        this.values = values;
    }
    [Symbol.iterator]() {
        return {
            next: () => {
                this.index++;
                if (this.index < this.size) {
                    return {
                        value: {
                            getKey: () => this.keys[this.index],
                            getValue: () => this.values[this.index],
                            isLastData: () => {
                                if (this.index == this.size - 1)
                                    return true;
                                return false;
                            },
                            isFirstData: () => {
                                if (this.index == 0)
                                    return true;
                                return false;
                            }
                        },
                        done: false
                    };
                }
                return {
                    value: null,
                    done: true
                };
            }
        };
    }
}
class ValuesEntry {
    constructor(values, size) {
        _mValues.set(this, []);
        _mIndex.set(this, -1);
        _mSize.set(this, 0);
        __classPrivateFieldSet(this, _mValues, values);
        __classPrivateFieldSet(this, _mSize, size);
    }
    [(_mValues = new WeakMap(), _mIndex = new WeakMap(), _mSize = new WeakMap(), Symbol.iterator)]() {
        return {
            next: () => {
                __classPrivateFieldSet(this, _mIndex, +__classPrivateFieldGet(this, _mIndex) + 1);
                if (__classPrivateFieldGet(this, _mIndex) < __classPrivateFieldGet(this, _mSize)) {
                    return {
                        value: {
                            getValue: () => __classPrivateFieldGet(this, _mValues)[__classPrivateFieldGet(this, _mIndex)],
                            isLastData: () => {
                                if (__classPrivateFieldGet(this, _mIndex) == __classPrivateFieldGet(this, _mSize) - 1)
                                    return true;
                                return false;
                            },
                            isFirstData: () => {
                                if (__classPrivateFieldGet(this, _mIndex) == 0)
                                    return true;
                                return false;
                            }
                        },
                        done: false
                    };
                }
                return {
                    value: undefined,
                    done: true
                };
            }
        };
    }
}
class EntryReverseIterable {
    constructor(keys, values, size) {
        this.keys = [];
        this.values = [];
        this.index = -1;
        this.size = 0;
        this.reverse = false;
        this.indexToFirst = 0;
        this.indexToEnd = 0;
        this.size = size;
        this.keys = keys;
        this.values = values;
        this.indexToFirst = -1;
        this.indexToEnd = size;
    }
    [Symbol.iterator]() {
        return {
            next: () => {
                this.index++;
                if (this.index < this.size) {
                    if (this.reverse) {
                        this.indexToEnd--;
                        this.reverse = false;
                        return {
                            value: {
                                getKey: () => this.keys[this.indexToEnd],
                                getValue: () => this.values[this.indexToEnd],
                                isLastData: () => {
                                    if (this.index == this.size - 1)
                                        return true;
                                    return false;
                                },
                                isFirstData: () => {
                                    if (this.index == 0)
                                        return true;
                                    return false;
                                }
                            },
                            done: false
                        };
                    }
                    else {
                        this.indexToFirst++;
                        this.reverse = true;
                        return {
                            value: {
                                getKey: () => this.keys[this.indexToFirst],
                                getValue: () => this.values[this.indexToFirst],
                                isLastData: () => {
                                    if (this.index == this.size - 1)
                                        return true;
                                    return false;
                                },
                                isFirstData: () => {
                                    if (this.index == 0)
                                        return true;
                                    return false;
                                }
                            },
                            done: false
                        };
                    }
                }
                return {
                    value: null,
                    done: true
                };
            }
        };
    }
}
/**
 * Public constants for type of inversement of the map.
 *
 * @class MapType
 * @version 1.0
 * @since 1.0
 */
class MapType {
}
exports.MapType = MapType;
/**
 * Reverse only keys
 *
 * @constant TYPE_KEYS
 * @type {number}
 * @since 1.0
 */
MapType.TYPE_KEYS = -1;
/**
 * Reverse only values
 *
 * @constant TYPE_VALUES
 * @type {number}
 * @since 1.0
 */
MapType.TYPE_VALUES = -2;
/**
 * The LinkedMap tool allows you to manipulate a map containing keys and values.
 *
 * LinkedMap is a way to create and manage a associative array.
 *
 * @class Map
 * @version 1.8
 * @since 1.0
 */
class Map {
    constructor() {
        this.keys = [];
        this.values = [];
        this.size = 0;
        this.PREFIX_EXCEPTION = "LinkedMap: ";
        this.MAXIMUM_CAPACITY = 1 << 30;
    }
    /**
     * Init a new object map of your primitives arrays keys and values.
     *
     * @param keys The primitive array keys.
     * @param values The primitive array values.
     * @returns Return the newst map.
     * @type {Map}
     * @since 1.6
     */
    static from(keys, values) {
        Utils_js_1.Preconditions.checkNotNull(keys, "primmitives array keys", "array");
        Utils_js_1.Preconditions.checkNotNull(values, "primmitives array values", "array");
        let map = new LinkedMap();
        if (keys.length == 0 && values.length == 0)
            return map;
        for (let i = 0; i < keys.length; i++)
            map.add(keys[i], values[i]);
        return map;
    }
    /**
     * Init a new object map of your primitive object.
     *
     * @param object Your primitive object.
     * @returns Return the newest map.
     * @type {Map}
     * @since 1.6
     */
    static fromObject(object) {
        let obj = object;
        if (obj == null)
            return new LinkedMap();
        let map = new LinkedMap();
        for (let key in obj)
            map.add(key, obj[key]);
        obj = null;
        return map;
    }
}
exports.Map = Map;
/**
 * The Entry class is a mean to log a key associated with its value.
 *
 * The Entry class is used by the EntryIterable class to obtain each entry to the map
 * and obtain each keys and values in a loop.
 *
 * @class Entry<K,V>
 * @version 1.2
 * @since 1.4
 */
Map.Entry = class Entry {
    /**
     * Get the key of the map
     *
     * @returns {String[]} the keys of the map
     * @since 1.0
     */
    getKey() { return null; }
    /**
     * Get the value of the map
     *
     * @return {Object[]} the value of the map
     * @since 1.0
     */
    getValue() { return null; }
    /**
     * Check if the indexing return to the last position.
     *
     * @returns {boolean} return true if the indexing is placed to the last position.
     * @since 1.5
     */
    isLastData() { return false; }
    /**
     * Check if the indexing return to the first position.
     *
     * @returns {boolean} return true is the indexing is placed to the first position.
     * @since 1.5
     */
    isFirstData() { return false; }
};
/**
 * The LinkedMap tool allows you to manipulate a map containing keys and values.
 *
 * LinkedMap is a way to create and manage a associative array.
 *
 * @class LinkedMap
 * @version 1.8
 * @since 1.0
 */
class LinkedMap extends Map {
    /**
     * Default constructor.
     */
    constructor() {
        super();
    }
    length() {
        return this.size;
    }
    containsValue(value) {
        return this.indexOf(value) >= 0;
    }
    containsKey(key) {
        return this.checkIfKeyExist(key) >= 0;
    }
    keyOf(value) {
        let index = this.indexOf(value);
        if (index >= 0)
            for (let i = 0; i < this.size; i++)
                if (index == i)
                    return this.keys[i];
        return null;
    }
    add(key, value) {
        if (this.size >= this.MAXIMUM_CAPACITY)
            throw new Exceptions_js_1.MaximumCapacityException(`${this.PREFIX_EXCEPTION}You have reached the maximum capacity of elements of the map. You have not permission to add a new value in the map.`);
        if (!Argument_js_1.Argument.isString(key))
            throw new Exceptions_js_1.ObjectCastException(`${this.PREFIX_EXCEPTION}The key must always be a string.`);
        if (Utils_js_1.UString.isEmpty(key))
            throw new Exceptions_js_1.NullPointerException(`${this.PREFIX_EXCEPTION}You cannot add a new value if the key is null or empty.`);
        if (this.checkIfKeyExist(key) >= 0)
            throw new Exceptions_js_1.IllegalArgumentException(`${this.PREFIX_EXCEPTION}You cannot add a new value because the key '${key}' is already existing in the map.`);
        this.keys[this.size] = key;
        this.values[this.size] = value;
        this.ensureIncrementSize(1);
    }
    addIfAbsent(key, value) {
        let v = this.get(key);
        if (v == null) {
            this.add(key, value);
            return true;
        }
        return false;
    }
    addAll(...maps) {
        for (let map of maps) {
            if (!Argument_js_1.Argument.isOf(map, "LinkedMap"))
                throw new Exceptions_js_1.ObjectCastException(`${this.PREFIX_EXCEPTION}${map.constructor.name} is not an instance of LinkedMap`);
            let size = map.length();
            if (size > 0)
                for (let entry of map.entries())
                    this.add(entry.getKey(), entry.getValue());
        }
    }
    get(key) {
        let index = this.checkIfKeyExist(key);
        if (index >= 0)
            for (let i = 0; i < this.size; i++)
                if (index == i)
                    return this.values[i];
        return null;
    }
    set(key, value) {
        let index = this.checkIfKeyExist(key);
        if (index >= 0) {
            for (let i = 0; i < this.size; i++)
                if (index == i)
                    this.values[i] = value;
            return;
        }
        else
            throw new Exceptions_js_1.IllegalArgumentException(`${this.PREFIX_EXCEPTION}The key ${key} Does not exist in the map.`);
    }
    remove(key) {
        let index = this.checkIfKeyExist(key);
        if (index >= 0) {
            let otherKeys = [];
            let otherValues = [];
            for (let i = 0, k = 0, v = 0; i < this.size; i++) {
                if (index == i)
                    continue;
                otherKeys[k++] = this.keys[i];
                otherValues[v++] = this.values[i];
            }
            this.keys = otherKeys;
            this.values = otherValues;
            this.ensureDecrementSize(1);
            otherKeys = null;
            otherValues = null;
        }
        else
            throw new Exceptions_js_1.IllegalArgumentException(`${this.PREFIX_EXCEPTION}The key ${key} Does not exist in the map.`);
    }
    clear() {
        for (let i = 0; i < this.size; i++) {
            this.keys[i] = null;
            this.values[i] = null;
        }
        this.size = 0;
        this.keys = [];
        this.values = [];
    }
    clearMemory() {
        this.keys.length = 0;
        this.values.length = 0;
        this.size = 0;
    }
    isEmpty() {
        return this.size == 0;
    }
    entries() {
        return new EntryIterable(this.keys, this.values, this.size);
    }
    entryValues() {
        return new ValuesEntry(this.values, this.size);
    }
    entriesReverse() {
        return new EntryReverseIterable(this.keys, this.values, this.size);
    }
    join(separator) {
        let builder = new StringBuilder_js_1.StringBuilder();
        for (let entry of this.entries()) {
            let key = entry.getKey();
            let value = entry.getValue();
            builder.append(`${key}${(Utils_js_1.UString.isEmpty(separator) ? "" : separator)}"${Argument_js_1.Argument.isPrimitive(value) ? value : value.constructor.name}"`);
        }
        return builder.toString();
    }
    reverse(type) {
        if (this.size > 0) {
            let otherKeys = [];
            let otherValues = [];
            for (let i = this.size - 1, k = 0, v = 0; i >= 0; i--) {
                otherKeys[k++] = this.keys[i];
                otherValues[v++] = this.values[i];
            }
            if (type == MapType.TYPE_KEYS)
                this.keys = otherKeys;
            else if (type == MapType.TYPE_VALUES)
                this.values = otherValues;
            else {
                this.keys = otherKeys;
                this.values = otherValues;
            }
            otherKeys = null;
            otherValues = null;
        }
    }
    toString() {
        let builder = new StringBuilder_js_1.StringBuilder();
        builder.append("[");
        for (let entry of this.entries()) {
            let key = entry.getKey();
            let value = entry.getValue();
            builder.append(`"${key}" => "${Argument_js_1.Argument.isPrimitive(value) ? value : value.constructor.name}"`)
                .append((entry.isLastData() ? "" : ","));
        }
        return builder.append("]").toString();
    }
    toJSON() {
        let builder = new StringBuilder_js_1.StringBuilder();
        builder.append("{");
        for (let entry of this.entries()) {
            let key = entry.getKey();
            let value = entry.getValue();
            if (!Argument_js_1.Argument.isPrimitive(value))
                throw new Exceptions_js_1.IllegalArgumentException(`${this.PREFIX_EXCEPTION}Your map contain an object. The JSON format can contain only primitive values [Float, Integer, Boolean, String].`);
            if (Argument_js_1.Argument.isString(value))
                builder.append(`"${key}": "${value}"`);
            else
                builder.append(`"${key}": ${value}`);
            builder.append((entry.isLastData() ? "" : ","));
        }
        return builder.append("}").toString();
    }
    toObject(enumerable) {
        let object = {};
        if (this.size > 0)
            for (let entry of this.entries())
                Object.defineProperty(object, entry.getKey(), {
                    value: entry.getValue(),
                    enumerable: enumerable
                });
        return object;
    }
    clone() {
        let map = new LinkedMap();
        if (this.size > 0)
            map.addAll(this);
        return map;
    }
    /**
     * Ensures the incrementation of the size to update the current size of the map
     *
     * @param value incremented value
     * @type {void}
     * @since 1.0
     */
    ensureIncrementSize(value) {
        this.size += value;
    }
    /**
     * Ensures the decrementation of the size to update the current size of the map
     *
     * @param value Decremented value
     * @type {void}
     * @since 1.0
     */
    ensureDecrementSize(value) {
        this.size -= value;
    }
    /**
     * Check if the key exist in the map and return the current index for get value
     *
     * @param key the key whose exist in the map is to be finded
     * @returns return the index of key
     * @type {number}
     * @since 1.0
     */
    checkIfKeyExist(key) {
        for (let i = 0; i < this.size; i++)
            if (this.keys[i] == key)
                return i;
        return -1;
    }
    /**
     * Return the index of the finded occurrence in the map
     * return -1 if the map does not contain the occurrence
     *
     * @param value value whose exist in the map is to be finded
     * @returns return the index of values
     * @type {number}
     * @since 1.0
     */
    indexOf(value) {
        for (let i = 0; i < this.size; i++)
            if (this.values[i] == value)
                return i;
        return -1;
    }
}
exports.LinkedMap = LinkedMap;
//# sourceMappingURL=LinkedMap.js.map